package com.qiangang.yan.tools.util;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.qiangang.yan.tools.annotation.CsvColumn;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description:
 * @Author: qiangang.yan
 * @CreateDate: 2019/8/12 17:12
 **/

public class CsvUtil<T> {

    private static Logger log = LoggerFactory.getLogger(CsvUtil.class);
    /**
     * CSV文件列分隔符
     */
    private static final String CSV_COLUMN_SEPARATOR = ",";

    /**
     * CSV文件数据出现和分隔符相同时的替换字符（也可以转译）
     */
    private static final String CSV_COLUMN_SEPARATOR_REPLACE = ".";

    /**
     * CSV文件换行符
     */
    private static final String CSV_RN = "\r\n";
    /**
     * CSV文件名前缀
     */
    private static final String FILE_PREFIX = "prefix_";


    /**
     * 被@CsvColumn注解的字段缓存
     * key：Class+group
     * value：Map<String, CsvColumn>
     * key：filedName  value：CsvColumn
     */
    private static final Cache<String, Map<String, CsvColumn>> annotationMapCache = CacheBuilder.newBuilder().build();
    /**
     * csv文件标题行数据缓存
     * key：Map<String, CsvColumn>对象的hashCode Hex
     * value：Csv行数据
     */
    private static final Cache<String, String> csvHeadLineCache = CacheBuilder.newBuilder().build();


    public static <T> void sendDataStream(HttpServletResponse response, List<T> dataList, String fileName, String group) {

        Class dataClass = dataList.get(0).getClass();
        final Map<String, CsvColumn> filedAnnotationMap = getFiledAnnotationMap(dataClass, group);
        //3.拼接头信息
        StringBuilder builder = new StringBuilder();
        builder.append(getCsvHeaderLine(filedAnnotationMap));
        builder.append(CSV_RN);
        //4.拼接数据列
        dataList.forEach(obj -> builder.append(getCsvOneLine(filedAnnotationMap, obj, dataClass)));
        //5.将数据写入response流中
        writeData(response, builder.toString(), fileName);
    }

    private static Map<String, CsvColumn> getFiledAnnotationMap(Class dataClass, String group) {
        return Optional.ofNullable(annotationMapCache.getIfPresent(getAnnotationMapKey(dataClass, group)))
                .orElseGet(() -> initFiledAnnotationMap(dataClass, group));
    }

    private static Map<String, CsvColumn> initFiledAnnotationMap(Class dataClass, String group) {
        Map<String, CsvColumn> columnMap = new LinkedHashMap<>();
        //1.查找带有@CsvColumn注解的field，并装入CsvColumnMap
        Arrays.asList(dataClass.getDeclaredFields()).forEach(field -> {
            com.qiangang.yan.tools.annotation.CsvColumn annotation = field.getDeclaredAnnotation(CsvColumn.class);
            if (annotation != null) {
                field.setAccessible(true);
                //剔除分组过滤的字段
                if ((StringUtils.isNotEmpty(group) && StringUtils.isNotEmpty(annotation.doGroup()) && !annotation.doGroup().equals(group)) ||
                        (StringUtils.isNotEmpty(annotation.unDoGroup()) && annotation.unDoGroup().equals(group))) {
                    return;
                }
                columnMap.put(field.getName(), annotation);
            }
        });
        //2.根据FileCsvColumn的weight属性对CsvColumnMap进行排序.
        final Map<String, CsvColumn> filedAnnotationMap = sortByValue(columnMap);
        //3.加入缓存
        annotationMapCache.put(getAnnotationMapKey(dataClass, group), filedAnnotationMap);
        return filedAnnotationMap;
    }

    /**
     * 拼接CVS表格一行数据
     *
     * @param filedAnnotationMap
     * @return
     */
    private static <T> String getCsvOneLine(Map<String, CsvColumn> filedAnnotationMap, T lineDate, Class dataClass) {
        StringBuilder lineStrBuilder = new StringBuilder();
        //1循环data，一个obj代表一行
        filedAnnotationMap.forEach((key, value) -> {
            //2循环filedAnnotationMap，一个Entity代表一列的数据
            try {
                Field field = dataClass.getDeclaredField(key);
                field.setAccessible(true);
                String dataColumn = Optional.ofNullable(field.get(lineDate)).orElse("").toString();
                //3内容中存在","将和默认的csv间隔符冲突，这里处理
                dataColumn = dataColumn.replaceAll(CSV_COLUMN_SEPARATOR, CSV_COLUMN_SEPARATOR_REPLACE);
                //4解析SpringEL表达式，处理自定义的输出格式需求
                if (StringUtils.isNotEmpty(value.springEL())) {
                    dataColumn = getSpringELValue(value.springEL(), lineDate);
                }
                lineStrBuilder.append(dataColumn);
                lineStrBuilder.append(CSV_COLUMN_SEPARATOR);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                log.error("CsvUtil根据反射操作属性异常，异常信息{}", e);
            }
        });
        return lineStrBuilder.append(CSV_RN).toString();
    }

    /**
     * 获取
     *
     * @param filedAnnotationMap
     * @return String
     */
    private static String getCsvHeaderLine(Map<String, CsvColumn> filedAnnotationMap) {
        return StringUtils.join(Collections.singleton(Optional.ofNullable(csvHeadLineCache.getIfPresent(Integer.toHexString(filedAnnotationMap.hashCode())))
                .orElseGet(() -> initCsvHeaderLine(filedAnnotationMap))), CSV_COLUMN_SEPARATOR);
    }

    /**
     * 获取annotationMapCache的key
     *
     * @param dataClass
     * @param group
     * @return
     */
    private static String getAnnotationMapKey(Class dataClass, String group) {
        return dataClass.getName().concat(Optional.ofNullable(group).orElse(""));
    }

    private static String initCsvHeaderLine(Map<String, CsvColumn> filedAnnotationMap) {
        final String csvHeaderLineStr = StringUtils.join(filedAnnotationMap.values().stream()
                .map(CsvColumn::title).collect(Collectors.toList()), CSV_COLUMN_SEPARATOR);
        csvHeadLineCache.put(Integer.toHexString(filedAnnotationMap.hashCode()), csvHeaderLineStr);
        return csvHeaderLineStr;
    }

    /**
     * 根据csv的内容 使用HttpServletResponse 发送
     *
     * @param data csv内容
     * @return
     */
    private static void writeData(HttpServletResponse response, String data, String fileName) {
        response.setContentType("application/csv");
        response.setHeader("Content-Disposition", "attachment;filename=" + getRealCsvFileName(fileName) + ".csv");
        data = "".concat(data);
        try {
            //ServletOutputStream outputStream = response.getOutputStream();
            OutputStreamWriter outputStream = new OutputStreamWriter(response.getOutputStream(), "gb2312");
            outputStream.write(data);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static String getSpringELValue(String springEL, Object sourceObj) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(springEL);
        return exp.getValue(sourceObj) + "";
    }

    /**
     * 根据FileColumn中的weight属性为Map<String, CsvColumn> map排序
     *
     * @param map 需要排序的map
     * @return
     */
    private static Map<String, CsvColumn> sortByValue(Map<String, CsvColumn> map) {
        Map<String, CsvColumn> result = new LinkedHashMap<>();
        map.entrySet().stream()
                .sorted(Comparator.comparing(entry -> entry.getValue().weight()))
                .forEach(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    private static String getRealCsvFileName(String fileName) {
        fileName = StringUtils.isEmpty(fileName) ? "default" : fileName.trim();
        return FILE_PREFIX
                .concat(fileName.replaceAll(" ", "-").concat("_"))
                .concat(System.currentTimeMillis() + "");
    }
}
